package com.xing.lei.happy.dao;

import com.xing.lei.happy.utils.*;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.Types;
import java.util.*;

@Service
public class Table {
	private Logger logger = LoggerFactory.getLogger( Table.class );
	private final Map<String, Map<String, Column>> columnss = new HashMap<String, Map<String, Column>>();
	private final Map<String, Column> primaryColumns = new HashMap<String, Column>();
	//@Qualifier("primaryDataSource")
	private DataSource dataSource;
	//@Autowired
	//private CommonService commonService;

	public Table(DataSource dataSource) {
		if (dataSource == null) {
			this.dataSource = BaseService.getDataSource();
		}else {
			this.dataSource = dataSource;
		}
	}

	private List<Column> getColumnListByResultSet(ResultSet rs1, ResultSet rs2)
			throws Exception {
		try {
			String primaryKey = null;
			while (rs1.next()) {
				primaryKey = DatabaseUtils.underline( rs1.getString( 4 ) );
			}
			List<Column> columnList = new ArrayList<Column>();
			while (rs2.next()) {
				String name = DatabaseUtils.underline( rs2
						.getString( "COLUMN_NAME" ) );
				Integer dataType = rs2.getInt( "DATA_TYPE" );
				Integer size = rs2.getInt( "COLUMN_SIZE" );
				Integer decimalDigits = rs2.getInt( "DECIMAL_DIGITS" );
				Column column = new Column();
				column.setPrimaryKey( name.equals( primaryKey ) );
				column.setName( name );
				column.setComment( StringUtils.toString( rs2.getObject( 12 ) ) );
				column.setDataType( dataType );
				column.setType( rs2.getString( "TYPE_NAME" ) );
				column.setSize( size );
				column.setDecimalDigits( decimalDigits );
				column.setNullable( rs2.getInt( "NULLABLE" ) );
				column.setField( DatabaseUtils.hump( name ) );
				column.setFieldType( parseFieldType( dataType, size,
						decimalDigits ) );
				columnList.add( column );
			}
			return columnList;
		} finally {
			DatabaseUtils.close( rs2, rs1 );
		}
	}

	private List<Column> getColumnListByTablename(String tablename) {
		if (ObjectUtils.hasBlank( dataSource, tablename )) {
			return new ArrayList<Column>();
		}
		Connection connection = null;
		ResultSet rs1 = null;
		ResultSet rs2 = null;
		try {
			connection = dataSource.getConnection();
			DatabaseMetaData metaData = connection.getMetaData();
			String schema = metaData.getUserName().toUpperCase();
			tablename = DatabaseUtils.underline( tablename ).toUpperCase();
			// commonService = (CommonService)ApplicationContextUtil.getApplicationContext().getBean("com.winning.rhm.common.service.commonService");
			//String dbName = commonService.getDBname();
		//	if (commonService.ORACLE_DB.equals( dbName )) {
				rs1 = metaData.getPrimaryKeys( null, schema, tablename );
				rs2 = metaData.getColumns( null, schema, tablename, "%" );
		//	} else if (commonService.MSSQL_DB.equals( dbName )) {
			//	rs1 = metaData.getPrimaryKeys( null, null, tablename );
		//		rs2 = metaData.getColumns( null, null, tablename, null );
		//	} else {
         //       rs1 = metaData.getPrimaryKeys( null, schema, tablename );
         //       rs2 = metaData.getColumns( null, schema, tablename, "%" );
      //      }
			List<Column> columnList = getColumnListByResultSet( rs1, rs2 );
			if (CollectionUtils.isNotEmpty( columnList )) {
				return columnList;
			}
			schema = metaData.getUserName().toLowerCase();
			tablename = tablename.toLowerCase();
			rs1 = metaData.getPrimaryKeys( null, schema, tablename );
			rs2 = metaData.getColumns( null, schema, tablename, "%" );
			return getColumnListByResultSet( rs1, rs2 );
		} catch (Exception e) {
			throw new RuntimeException( e );
		} finally {
			if (connection != null) {
				try {
					connection.close();
				} catch (Exception re) {
					logger.error( re.getMessage() );
				}
			}
			if (rs1 != null) {
				try {
					rs1.close();
				} catch (Exception re) {
					logger.error( re.getMessage() );
				}
			}
			if (rs2 != null) {
				try {
					rs2.close();
				} catch (Exception re) {
					logger.error( re.getMessage() );
				}
			}
		}
	}

	private String init(Object obj) {
		String tablename = DatabaseUtils.underline( obj );
		if (MapUtils.notContainsKey( columnss, tablename )) {
			synchronized (columnss) {
				if (MapUtils.notContainsKey( columnss, tablename )) {
					Map<String, Column> columnMap = null;
					List<Column> columnList = getColumnListByTablename( tablename );
					if (ObjectUtils.isNotBlank( columnList )) {
						columnMap = new OrderedMap<String, Column>();
						for (Column column : columnList) {
							columnMap.put( column.getName(), column );
							if (column.isPrimaryKey()) {
								primaryColumns.put( tablename, column );
							}
						}
					}
					columnss.put( tablename, columnMap );
				}
			}
		}
		return tablename;
	}

	private Class<?> parseFieldType(Integer dataType, Integer size,
									Integer decimalDigits) {
		if (dataType == null) {
			return ObjectUtils.class;
		}
		switch (dataType) {
			case Types.ARRAY:
				return Object.class;
			case Types.BIGINT:
				return Long.class;
			case Types.BINARY:
				return Object.class;
			case Types.BIT:
				return Boolean.class;
			case Types.BLOB:
				return byte[].class;
			case Types.BOOLEAN:
				return Boolean.class;
			case Types.CHAR:
				return String.class;
			case Types.CLOB:
				return String.class;
			case Types.DATALINK:
				return Object.class;
			case Types.DATE:
				return Date.class;
			case Types.DECIMAL:
			case Types.NUMERIC:
				if (NumberUtils.greater( decimalDigits, 0 )
						|| NumberUtils.greater( size, 18 )) {
					return BigDecimal.class;
				} else if (NumberUtils.greater( size, 9 )) {
					return Long.class;
				} else if (NumberUtils.greater( size, 4 )) {
					return Integer.class;
				} else {
					return Short.class;
				}
			case Types.DISTINCT:
				return Object.class;
			case Types.DOUBLE:
				return Double.class;
			case Types.FLOAT:
				return Double.class;
			case Types.INTEGER:
				return Integer.class;
			case Types.JAVA_OBJECT:
				return Object.class;
			case Types.LONGVARBINARY:
				return byte[].class;
			case Types.LONGVARCHAR:
				return String.class;
			case Types.NULL:
				return Object.class;
			case Types.OTHER:
				return Object.class;
			case Types.REAL:
				return Float.class;
			case Types.REF:
				return Object.class;
			case Types.SMALLINT:
				return Short.class;
			case Types.STRUCT:
				return Object.class;
			case Types.TIME:
				return Date.class;
			case Types.TIMESTAMP:
				return Date.class;
			case Types.TINYINT:
				return Byte.class;
			case Types.VARBINARY:
				return byte[].class;
			case Types.VARCHAR:
				return String.class;
			default:
				return Object.class;
		}
	}

	public Map<String, Column> getColumns(Object obj) {
		return columnss.get( init( obj ) );
	}

	public Column getColumn(Object obj, String name) {
		name = DatabaseUtils.underline( name );
		return (Column) MapUtils.getObject( getColumns( obj ), name );
	}

	public boolean hasColumn(Object obj, String name) {
		name = DatabaseUtils.underline( name );
		return MapUtils.containsKey( getColumns( obj ), name );
	}

	public Column getPrimaryColumn(Object obj) {
		return primaryColumns.get( init( obj ) );
	}

	public boolean hasPrimaryColumn(Object obj) {
		return primaryColumns.containsKey( init( obj ) );
	}

	public boolean tableExists(String tablename) {
		Connection connection = null;
		try {
			connection = dataSource.getConnection();
			DatabaseMetaData metaData = connection.getMetaData();
			String schema = metaData.getUserName().toUpperCase();
			tablename = DatabaseUtils.underline(tablename).toUpperCase();
			ResultSet tables = metaData.getTables(null, schema, tablename, null);
			return tables.next();
		} catch (Exception e) {
			throw new RuntimeException( e );
		} finally {
			if (connection != null) {
				try {
					connection.close();
				} catch (Exception re) {
					logger.error( re.getMessage() );
				}
			}
		}
	}
}